// file:include/os/permission.c
// autor:jiangxinpeng
// time:2021.3.3
// copyright:(C) by jiangxinpeng,All right are reserved.

#include <os/account.h>
#include <os/kernelif.h>
#include <os/fs.h>
#include <os/debug.h>
#include <os/memcache.h>
#include <os/mutexlock.h>
#include <os/sconfig.h>
#include <os/debug.h>
#include <lib/string.h>
#include <lib/unistd.h>
#include <lib/errno.h>
#include <lib/type.h>
#include <sys/fcntl.h>

extern permiss_database_t *permiss_db;

// init permissdata base
void PermissionDataBaseInit()
{
    permiss_db = KMemAlloc(sizeof(permiss_database_t));

    if (!permiss_db)
    {
        KPrint("alloc memory to permission database failed! size=%d\n", sizeof(permiss_database_t));
    }
    // init
    MutexlockInit(&permiss_db->lock);
    permiss_db->len = 0;
    for (int i = 0; i < PERMISS_DATABASE_LEN; i++)
    {
        PermissionDataInit(permiss_db->data + i, 0, NULL);
    }
    PermissionDataBaseLoad();
    KPrint(PRINT_INFO "permission database load done!\n");
}

// set perssion data
void PermissionDataInit(permiss_data_t *data, uint32_t attr, char *str)
{
    memset(data, 0, sizeof(permiss_db));
    data->attr = attr;
    if (str != NULL)
    {
        strcpy(data->str, str);
    }
}

int PerssionDataBaseSearchFree()
{
    int i = 0;

    for (i = 0; i <= PERMISS_DATABASE_LEN; i++)
    {
        if (permiss_db->data[i].attr)
            continue;
        break;
    }
    if (i < PERMISS_DATABASE_LEN)
        return i;
    return -1;
}

// insert permiss data to permission database
int PermissionDataBaseInsert(uint32_t attr, char *str)
{
    int pos;

    MutexlockLock(&permiss_db->lock, MUTEX_LOCK_MODE_BLOCK);
    // check length
    if (permiss_db->len < PERMISS_DATABASE_LEN)
    {
        // get free position
        pos = PerssionDataBaseSearchFree();
        if (pos >= 0)
        {
            PermissionDataInit(permiss_db->data + pos, attr, str);
            permiss_db->len++;
            MutexlockUnlock(&permiss_db->lock);
            return 0;
        }
    }
    MutexlockUnlock(&permiss_db->lock);
    return -1;
}

// delete permission database
int PermissionDataBaseDel(int pos)
{
    if (pos < 0)
        return -1;

    MutexlockLock(&permiss_db->lock, MUTEX_LOCK_MODE_BLOCK);
    if ((pos < PERMISS_DATABASE_LEN))
    {
        PermissionDataInit(permiss_db->data + pos, 0, NULL);
        permiss_db->len--;
        MutexlockUnlock(&permiss_db->lock);
        return 0;
    }
    MutexlockUnlock(&permiss_db->lock);
    return -1;
}

int PermissionDataBaseDelByData(char *str)
{
    MutexlockLock(&permiss_db->lock, MUTEX_LOCK_MODE_BLOCK);

    for (int i = 0; i < PERMISS_DATABASE_LEN; i++)
    {
        if (!strcmp(permiss_db->data[i].str, str))
        {
            PermissionDataInit((permiss_data_t *)permiss_db->data + i, 0, NULL);
            permiss_db->len--;
            MutexlockUnlock(&permiss_db->lock);
            return 0;
        }
    }
    MutexlockUnlock(&permiss_db->lock);
    return -1;
}

void PermissionDataBaseForEach(void (*callback)(void *, void *), void *arg)
{
    for (int i = 0; i < PERMISS_DATABASE_LEN; i++)
    {
        if (permiss_db->data[i].attr)
        {
            callback(arg, &permiss_db->data[i]);
        }
    }
}

permiss_data_t *PermissionDataBaseSelect(char *str)
{
    permiss_data_t *data = NULL;

    MutexlockLock(&permiss_db->lock, MUTEX_LOCK_MODE_BLOCK);

    for (int i = 0; i < PERMISS_DATABASE_LEN; i++)
    {
        data = permiss_db->data + i;
        if (!strcmp(data->str, str) && data->attr)
        {
            MutexlockUnlock(&permiss_db->lock);
            return data;
        }
    }
    MutexlockUnlock(&permiss_db->lock);
    return NULL;
}

void PermissionDataToStr(permiss_data_t *data, void *buff)
{
    char str[8];

    // permission type
    if (data->attr & PERMISS_ATTR_DEVICE)
    {
        str[0] = '0';
    }
    else
    {
        if (data->attr & PERMISS_ATTR_FILE)
        {
            str[0] = '1';
        }
        else
        {
            if (data->attr & PERMISS_ATTR_FIFO)
            {
                str[0] = '2';
            }
            else
            {
                str[0] = '-';
            }
        }
    }
    // permission attribute
    if (data->attr & PERMISS_ATTR_READ)
    {
        str[1] = 'R';
    }
    else
    {
        str[1] = '-';
    }

    if (data->attr & PERMISS_ATTR_WRITE)
    {
        str[2] = 'W';
    }
    else
    {
        str[2] = '-';
    }

    if (data->attr & PERMISS_ATTR_EXEC)
    {
        str[3] = 'X';
    }
    else
    {
        str[3] = '-';
    }

    if (data->attr & PERMISS_ATTR_HOME)
    {
        str[4] = 'H';
    }
    else
    {
        str[5] = '-';
    }
    // write config to buff
    SConfigWrite(buff, str);
    SConfigWrite(buff, data->str);
    SConfigWriteLine(buff);
}

// auto insert permission database
int PermissionAutoInsert()
{
    PermissionDataBaseInsert(PERMISS_ATTR_DEVICE | PERMISS_ATTR_RDWR, "disk0");
    PermissionDataBaseInsert(PERMISS_ATTR_DEVICE | PERMISS_ATTR_RDWR, "disk1");
    PermissionDataBaseInsert(PERMISS_ATTR_FILE | PERMISS_ATTR_RDWR, ACCOUNT_DIR_PATH);
    PermissionDataBaseInsert(PERMISS_ATTR_FILE | PERMISS_ATTR_READ, "/sys/init");
    if (!PermissionDataBaseSync())
    {
        return 0;
    }
    return -1;
}

int PermissionDataBaseSync()
{
    int fd;
    permiss_data_t *data;
    char buff[MAX_PATH_LEN+1];

    memset(buff, 0, MAX_PATH_LEN);

    MutexlockLock(&permiss_db->lock, MUTEX_LOCK_MODE_BLOCK);
    strcat(buff, ACCOUNT_DIR_PATH);
    strcat(buff, "/");
    strcat(buff, PERMISSION_FILE_NAME);
    fd = KFileOpen(buff, O_RDWR);
    if (fd < 0)
    {
        KPrint("open permission database file failed! file:%s", buff);
        MutexlockUnlock(&permiss_db->lock);
        return -1;
    }
    // write to file
    for (int i = 0; i < PERMISS_DATABASE_LEN; i++)
    {
        data = permiss_db->data + i;
        if (data->attr)
        {
            char data_buff[PERMISS_STRING_LEN + 12] = {0};
            PermissionDataToStr(data, data_buff);
            KFileWrite(fd, data_buff, strlen(data_buff));
        }
    }
    KFileClose(fd);
    MutexlockUnlock(&permiss_db->lock);
    return 0;
}

int PermissionScanLine(char *str)
{
    uint32_t attr;
    char attrbuff[32];
    char databuff[32];
    char *p = attrbuff;
    char *q = str;
    // parse attribute
    q = SConfigRead(q, attrbuff, 32);
    if (!q)
    {
        return -1;
    }
    if (*p == '0')
    {
        attr |= PERMISS_ATTR_DEVICE;
    }
    else
    {
        if (*p == '1')
        {
            attr |= PERMISS_ATTR_FILE;
        }
        else
        {
            if (*p == '2')
            {
                attr |= PERMISS_ATTR_FIFO;
            }
        }
    }
    p++;
    if (*p == 'R')
    {
        attr |= PERMISS_ATTR_READ;
    }
    p++;
    if (*p == 'W')
    {
        attr |= PERMISS_ATTR_WRITE;
    }
    p++;
    if (*p == 'X')
    {
        attr |= PERMISS_ATTR_EXEC;
    }
    p++;
    if (*p == 'H')
    {
        attr |= PERMISS_ATTR_HOME;
    }
    q = SConfigRead(q, databuff, 32);
    if (!q)
        return -1;
    SConfigTrim(databuff);
    return PermissionDataBaseInsert(attr, databuff);
}

static int PermissionScanInsert(char *file)
{
    int fd = KFileOpen(file, O_RDONLY);
    int size;
    char *buff;
    char *p = buff;
    char *line = KMemAlloc(PERMISS_DATA_MAX);

    if (fd < 0)
        return -1;
    size = KFileLSeek(fd, 0, SEEK_END);
    if (size <= 0)
    {
        KFileClose(fd);
        return -1;
    }
    buff = KMemAlloc(size);
    if (!buff)
    {
        KFileClose(fd);
        return -1;
    }
    memset(buff, 0, size);
    KFileLSeek(fd, 0, SEEK_SET);
    if (KFileRead(fd, buff, size) != size)
    {
        KMemFree(buff);
        KFileClose(fd);
        return -1;
    }
    KFileClose(fd);
    // start parse data
    while (1)
    {
        p = SConfigReadLine(p, line, PERMISS_DATA_MAX);
        if (*p)
        {
            KPrint("primission string:%s\n", line);
            if (PermissionScanLine(line) < 0)
                KPrint("permission: insert database failed!\n");
        }
        break;
    }
    KMemFree(buff);
    KMemFree(line);
    return 0;
}

permiss_data_t *PermissionDataBaseSelectByIndex(int idx)
{
    if (idx > PERMISS_STRING_LEN)
        return NULL;
    return permiss_db->data + idx;
}

int PermissionDataBaseLoad()
{
    char buff[32];
    int fd;
    int file_no_exist = 0;

    memset(buff, 0, 32);

    strcat(buff, ACCOUNT_DIR_PATH);
    strcat(buff, "/");
    strcat(buff, PERMISSION_FILE_NAME);
    if (KFileAccess(buff, F_OK) < 0) // file no exist
    {
        fd = KFileOpen(buff, O_CREATE | O_RDWR);

        if (fd < 0)
        {
            KPrint(PRINT_ERR "create permission file %s failed!\n", buff);
            return -1;
        }

        KFileClose(fd);
        file_no_exist = 1;
    }

    if (file_no_exist)
    {
        if (PermissionAutoInsert() < 0)
        {
            KPrint("permission auto insert %s failed!\n", buff);
            return -1;
        }
    }
    else
    {
        // scan file and insert permission data
        if (PermissionScanInsert(buff) < 0)
        {
            KPrint(PRINT_ERR "permission file %s scan insert failed!\n", buff);
            return -1;
        }
    }
    return 0;
}